﻿using System;
using System.Collections.Generic;
using System.Reflection;
using System.Linq;
using System.IO;
using System.Windows.Media.Imaging;
using System.Net;
using System.Windows;
using System.Runtime.Serialization;
using System.IO.IsolatedStorage;
using System.Windows.Media;



namespace Motif.Serialization
{
    public class CustomBinarySerializer
    {

        private List<PropertyInfo> serializableProperties = new List<PropertyInfo>();
        private Type serializableObjectType;

        public CustomBinarySerializer(Type objectType)
        {
            serializableObjectType = objectType;
            serializableProperties = GetMarkedProperties(objectType);
        }

        private List<PropertyInfo> GetMarkedProperties(Type type)
        {
            return (from property in type.GetProperties()
                    where property.GetCustomAttributes(true)
                    .Where((x) => x is System.Runtime.Serialization.DataMemberAttribute).Count() > 0
                    select property
                    ).ToList();
        }

        #region Write

        public void WriteObject(Stream stream, object graph)
        {
            if (stream == null || graph == null)
                return;

            BinaryWriter bw = new BinaryWriter(stream);

            WriteObject(bw, graph);
        }

        public void WriteObject(BinaryWriter bw, object graph)
        {

            foreach (PropertyInfo pi in serializableProperties)
            {
                var value = pi.GetValue(graph, null);

                if (pi.PropertyType == typeof(string))
                {
                    bw.Write(value as string ?? string.Empty);
                }
                else if (pi.PropertyType == typeof(List<int>))
                {
                    WriteIntegerList(bw, value as List<int>);
                }
                else if (pi.PropertyType == typeof(double))
                {
                    bw.Write((double)value);
                }
                else if (pi.PropertyType == typeof(System.Windows.Point))
                {
                    bw.Write(((System.Windows.Point)value).X);
                    bw.Write(((System.Windows.Point)value).Y);
                }
                else if (pi.PropertyType == typeof(WriteableBitmap))
                {
                    WriteBitmap(bw, value as WriteableBitmap);
                }
                else if (pi.PropertyType == typeof(Dictionary<int, string>))
                {
                    WriteDictionary(bw, value as Dictionary<int, string>);
                }
                else if (pi.PropertyType == typeof(Dictionary<int, int>))
                {
                    WriteDictionary(bw, value as Dictionary<int, int>);
                }
                else if (pi.PropertyType == typeof(Matrix))
                {
                    WriteMatrix(bw, (Matrix)value);
                }
                else if (pi.PropertyType == typeof(System.Windows.Size))
                {
                    bw.Write(((System.Windows.Size)value).Width);
                    bw.Write(((System.Windows.Size)value).Height);
                }
                else throw new Exception("undefined serialization");
            }
        }

        private void WriteDictionary(BinaryWriter bw, Dictionary<int, string> list)
        {
            if (list == null || !list.Any())
            {
                bw.Write(0);
            }
            else
            {
                bw.Write(list.Count);
                list.ToList().ForEach(x =>
                {
                   bw.Write(x.Key);
                   bw.Write(x.Value);
                });
            }
        }
        private void WriteDictionary(BinaryWriter bw, Dictionary<int, int> list)
        {
            if (list == null || !list.Any())
            {
                bw.Write(0);
            }
            else
            {
                bw.Write(list.Count);
                list.ToList().ForEach(x =>
                {
                    bw.Write(x.Key);
                    bw.Write(x.Value);
                });
            }
        }
        private void WriteIntegerList(BinaryWriter bw, List<int> list)
        {
            if (list == null || !list.Any())
            {
                bw.Write(0);
            }
            else
            {
                bw.Write(list.Count);
                list.ForEach(x => bw.Write(x));
            }
        }
        private void WriteBitmap(BinaryWriter bw, WriteableBitmap bmp)
        {
            bw.Write(bmp.PixelWidth);
            bw.Write(bmp.PixelHeight);
            //byte[] bytes = 
            Compress(bmp, bw);
            //bw.Write(bytes.Length);
            // compressedFile.CopyTo(bw.BaseStream);
            //bw.Write(bytes);

            //bmp.Pixels.ToList().ForEach(x => bw.Write(x));

        }
        private void WriteMatrix(BinaryWriter bw, Matrix matrix)
        {
            bw.Write(matrix.IsIdentity);
            bw.Write(matrix.M11);
            bw.Write(matrix.M12);
            bw.Write(matrix.M21);
            bw.Write(matrix.M22);
            bw.Write(matrix.OffsetX);
            bw.Write(matrix.OffsetY);
        }
        public void Compress(WriteableBitmap wb, BinaryWriter bw)
        {
            // Create a file name for the JPEG file in isolated storage.
            String tempJPEG = "TempJPEG";

            // Create a virtual store and file stream. Check for duplicate tempJPEG files.
            var myStore = IsolatedStorageFile.GetUserStoreForApplication();
            if (myStore.FileExists(tempJPEG))
            {
                myStore.DeleteFile(tempJPEG);
            }

            IsolatedStorageFileStream myFileStream = myStore.CreateFile(tempJPEG);

            // Encode the WriteableBitmap object to a JPEG stream.
            wb.SaveJpeg(myFileStream, wb.PixelWidth, wb.PixelHeight, 0, 85);
            myFileStream.Close();


            // Create a new stream from isolated storage, and save the JPEG file to the media library on Windows Phone.
            myFileStream = myStore.OpenFile(tempJPEG, FileMode.Open, FileAccess.Read);
            //BinaryReader br = new BinaryReader(myFileStream);
            bw.Write(myFileStream.Length);

            byte[] bytes = new byte[4096];
            int numBytesToRead = (int)myFileStream.Length;
            int numBytesRead = 0;
            while (numBytesToRead > 0)
            {
                // Read may return anything from 0 to 10.
                int n = myFileStream.Read(bytes, 0, numBytesToRead < bytes.Length ? numBytesToRead : bytes.Length);
                bw.Write(bytes, 0, n);
                // The end of the file is reached.
                if (n == 0)
                {
                    break;
                }
                numBytesRead += n;
                numBytesToRead -= n;
            }

            //while (myFileStream.Position < myFileStream.Length)
            //{
            //    byte inputChar = (byte)myFileStream.ReadByte();
            //    bw.Write(inputChar);
            //}

            // Determine the size of the IsolatedStorageFileStream
            // by checking its Length property.
            // br.ReadBytes((int)myFileStream.Length);

            //byte[] bytes = new byte[myFileStream.Length];
            //int numBytesToRead = (int)myFileStream.Length;
            //int numBytesRead = 0;
            //while (numBytesToRead > 0)
            //{
            //    // Read may return anything from 0 to 10.
            //    int n = myFileStream.Read(bytes, numBytesRead, numBytesToRead < 1000 ? numBytesToRead : 1000);
            //    // The end of the file is reached.
            //    if (n == 0)
            //    {
            //        break;
            //    }
            //    numBytesRead += n;
            //    numBytesToRead -= n;
            //}
            myFileStream.Close();

            //return bytes;
        }


        #endregion Write

        #region Read

        public object ReadObject(Stream stream)
        {
            if (stream == null)
                return null;

            BinaryReader br = new BinaryReader(stream);

            return ReadObject(br);
        }

        public object ReadObject(BinaryReader br)
        {


            object deserializedObject = Activator.CreateInstance(serializableObjectType);
    
            
            foreach (PropertyInfo pi in serializableProperties)
            {
                if (pi.PropertyType == typeof(string))
                {
                    pi.SetValue(deserializedObject, br.ReadString(), null);
                }
                else if (pi.PropertyType == typeof(List<int>))
                {
                    pi.SetValue(deserializedObject, ReadIntegerList(br), null);
                }
                else if (pi.PropertyType == typeof(double))
                {
                    pi.SetValue(deserializedObject, br.ReadDouble(), null);
                }
                else if (pi.PropertyType == typeof(System.Windows.Point))
                {
                    double X = br.ReadDouble();
                    double Y = br.ReadDouble();
                    pi.SetValue(deserializedObject, new System.Windows.Point(X, Y), null);

                }
                else if (pi.PropertyType == typeof(WriteableBitmap))
                {
                    pi.SetValue(deserializedObject, ReadBitmap(br), null);

                }
                else if (pi.PropertyType == typeof(Dictionary<int,string>))
                {
                    pi.SetValue(deserializedObject, ReadDictionary(br), null);

                }
                else if (pi.PropertyType == typeof(Dictionary<int, int>))
                {
                    pi.SetValue(deserializedObject, ReadDictionaryInt(br), null);

                }
                else if (pi.PropertyType == typeof(System.Windows.Media.Matrix))
                {
                    pi.SetValue(deserializedObject, ReadMatrix(br), null);

                }
                else if (pi.PropertyType == typeof(System.Windows.Size))
                {
                    double wdt = br.ReadDouble();
                    double hgt = br.ReadDouble();
                    pi.SetValue(deserializedObject, new System.Windows.Size(wdt, hgt), null);

                }
                else throw new Exception("undefined deserialization");


            }

            return deserializedObject;
        }

        private Dictionary<int, string> ReadDictionary(BinaryReader br)
        {
            Dictionary<int, string> dict = new Dictionary<int, string>();
            int count = br.ReadInt32();

            int index = count;
            while (index > 0)
            {
                int key = br.ReadInt32();
                string value = br.ReadString();
                dict.Add(key,value);
                index--;
            }
            return dict;
        }
        private Dictionary<int, int> ReadDictionaryInt(BinaryReader br)
        {
            Dictionary<int, int> dict = new Dictionary<int, int>();
            int count = br.ReadInt32();

            int index = count;
            while (index > 0)
            {
                int key = br.ReadInt32();
                int value = br.ReadInt32();
                dict.Add(key, value);
                index--;
            }
            return dict;
        }
        private List<int> ReadIntegerList(BinaryReader br)
        {
            List<int> list = new List<int>();
            int count = br.ReadInt32();

            int index = count;
            while (index > 0)
            {
                list.Add(br.ReadInt32());
                index--;
            }
            return list;
        }


        private static WriteableBitmap ReadBitmap(BinaryReader br)
        {
            int wdt = br.ReadInt32();
            int hgt = br.ReadInt32();
            long len = br.ReadInt64();
            WriteableBitmap bmp = new WriteableBitmap(wdt, hgt);
            bmp = Decompress(br, len, bmp);

            return bmp;
        }
        
        private Matrix ReadMatrix(BinaryReader br)
        {
            bool IsIdentity = br.ReadBoolean();
            double M11 = br.ReadDouble();
            double M12 = br.ReadDouble();
            double M21 = br.ReadDouble();
            double M22 = br.ReadDouble();
            double OffsetX = br.ReadDouble();
            double OffsetY = br.ReadDouble();
            Matrix matrix = new Matrix(M11, M12, M21, M22, OffsetX, OffsetY);

            return matrix;
        }
        public static WriteableBitmap Decompress(BinaryReader br, long len, WriteableBitmap wb)
        {

            // Create a file name for the JPEG file in isolated storage.
            String tempJPEG = "TempJPEG";

            // Create a virtual store and file stream. Check for duplicate tempJPEG files.
            var myStore = IsolatedStorageFile.GetUserStoreForApplication();
            if (myStore.FileExists(tempJPEG))
            {
                myStore.DeleteFile(tempJPEG);
            }

            IsolatedStorageFileStream myFileStream = myStore.CreateFile(tempJPEG);

            byte[] bytes = new byte[4096];
            int numBytesToRead = (int)len;
            int numBytesRead = 0;
            while (numBytesToRead > 0)
            {
                // Read may return anything from 0 to 10.
                int n = br.Read(bytes, 0, numBytesToRead < bytes.Length ? numBytesToRead : bytes.Length);
                myFileStream.Write(bytes, 0, n);
                // The end of the file is reached.
                if (n == 0)
                {
                    break;
                }
                numBytesRead += n;
                numBytesToRead -= n;
            }
            //for (int i = 0; i < len; i++)
            //{

            //    byte inputChar = br.ReadByte();
            //    myFileStream.WriteByte(inputChar);
            //}

            //byte[] bytes = new byte[len];
            //int numBytesToRead = (int)len;
            //int numBytesRead = 0;
            //while (numBytesToRead > 0)
            //{
            //    // Read may return anything from 0 to 10.
            //    int n = br.Read(bytes, numBytesRead, numBytesToRead < 1000 ? numBytesToRead : 1000);
            //    myFileStream.Write(bytes, numBytesRead, numBytesToRead);
            //    // The end of the file is reached.
            //    if (n == 0)
            //    {
            //        break;
            //    }
            //    numBytesRead += n;
            //    numBytesToRead -= n;
            //}


            myFileStream.Close();

            // Create a new stream from isolated storage, and save the JPEG file to the media library on Windows Phone.
            myFileStream = myStore.OpenFile(tempJPEG, FileMode.Open, FileAccess.Read);
            // Encode the WriteableBitmap object to a JPEG stream.

            wb.LoadJpeg(myFileStream);

            myFileStream.Close();


            return wb;
        }
       

       

        #endregion Read

    }
}
